package com.xiashitech.agent.instrumentation.biz.userinfo.httprequest.inst;

import com.xiashitech.agent.cache.status.CacheStatus;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.constants.SpecialConfigString;
import com.xiashitech.agent.dto.UserRequest;
import com.xiashitech.agent.instrumentation.bridge.Java8Bridge;
import com.xiashitech.agent.instrumentation.httprequest.XSHttpServletRequestWrapper;
import com.xiashitech.agent.utils.DurationPrinter;
import com.xiashitech.agent.utils.TypeUtils;
import com.xiashitech.agent.utils.XSUtil;
import com.xiashitech.interfaces.agent.dto.AgentConfigDTO;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Scope;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class XSHttpRequestInstrumentation implements TypeInstrumentation {

    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        return hasSuperType(namedOneOf("javax.servlet.http.HttpServlet","javax.servlet.http.HttpFilter"));
    }

    @Override
    public void transform(TypeTransformer typeTransformer) {
        typeTransformer.applyAdviceToMethod(
                isMethod().and(namedOneOf("service","doFilter"))
                        .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
                        .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse")))
                        .and(isProtected()),
                XSHttpRequestInstrumentation.class.getName() + "$InnerAdvice");
    }

    @SuppressWarnings("unused")
    public static class InnerAdvice {

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void onEnter(
                @Advice.Argument(value = 0, readOnly = false) HttpServletRequest request,
                @Advice.Argument(value = 1, readOnly = false) HttpServletResponse response,
                @Advice.Local("user") UserRequest userRequest,
                @Advice.Local("otelScope") Scope scope) {
            DurationPrinter durationPrinter = new DurationPrinter("XSHttpRequestInstrumentation");
            try {
                if (CacheStatus.cannotRun()) {
                    return;
                }

                if(TypeUtils.isSkipAcceptType(request.getHeader("Accept"), request.getRequestURI())) {
                    return;
                }

                String userIdPath = XSUtil.getAgentConfigBySystemId(AgentConfigDTO.getConfig().getUseridpath());
                if (XSUtil.isEmpty(userIdPath) || !userIdPath.startsWith(SpecialConfigString.BIZ_USER_REQUEST.getConfiString())){
                    return;
                }

                if (XSUtil.isNotEmpty(Java8Bridge.currentForBaggage().getEntryValue("xs.userId"))){
                    return;
                }
                request = new XSHttpServletRequestWrapper(request);
                scope = Java8Bridge.currentForBaggage().toBuilder().put("xs.biz.state", "start").build().makeCurrent();
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
            durationPrinter.printElapseDuration("InnerAdvice b");
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void onExit(@Advice.Local("otelScope") Scope scope) {
            DurationPrinter durationPrinter = new DurationPrinter("XSHttpRequestInstrumentation");
            try {
                if (scope == null) {
                    return;
                }
                scope.close();
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
            durationPrinter.printElapseDuration("InnerAdvice e");
        }
    }

}
